package dao

import (
	"database/sql"
	gorm_v2 "gitee.com/youbeiwuhuan/gin-practice/gin-gorm2/tools/gormv2"
	"gitee.com/youbeiwuhuan/gin-practice/gin-gorm2/tools/reflects"
	"gorm.io/gorm"
	"math"
	"reflect"
)

const (
	// 分页器时，默认允许最大的记录数
	MaxLimit = 1000
)

type IBaseDao[T any] interface {
	GetDb() *gorm.DB

	// 插入返回id
	insert(entityData T) int64
	// GetById 根据id查询
	GetById(id int64) T

	// UpdateById 更新返回影响行数
	UpdateById(entityData T) int64

	// DeleteById 删除返回影响行数
	DeleteById(id int64) int64

	// BeginTx 开启事务：开启事务之后，必须使用开启事务返回的*gorm.DB, 而不是开启事务时使用*gorm.DB
	BeginTx(opts ...*sql.TxOptions) *gorm.DB
	// RollbackTx 回滚事务，参数是BeginTx返回值
	RollbackTx(dbTx *gorm.DB)

	// CommitTx 提交事务，参数是BeginTx返回值
	CommitTx(dbTx *gorm.DB)

	// ExecTx 执行事务
	// @Param txFn 要执行的逻辑
	// @Param opts 事务选项
	ExecTx(txFn func(*gorm.DB) (error, interface{}), opts ...*sql.TxOptions) (error, interface{})
}

// BaseDao 初始化 gorm 实例的其他字段
type BaseDao[T any] struct {
	db        *gorm.DB
	model     T
	modelType reflect.Type
}

func NewBaseDao[T any](db0 *gorm.DB, model0 T) *BaseDao[T] {
	if !reflects.IsStruct(model0) {
		panic("model0 is not a struct !")
	}

	return &BaseDao[T]{
		db:        db0,
		model:     model0,
		modelType: reflect.TypeOf(&model0),
	}
}

func (dao *BaseDao[T]) insert(entityData T) int64 {
	tx := dao.db.Save(&entityData)
	return tx.RowsAffected
}

func (dao *BaseDao[T]) GetById(id int64) T {
	u := dao.model
	// 根据主键获取记录（仅适用于整型主键）
	dao.db.First(&u, id)

	return u
}

func (dao *BaseDao[T]) UpdateById(entityData T) int64 {
	tx := dao.db.Model(&dao.model).Updates(entityData)
	return tx.RowsAffected
}

func (dao *BaseDao[T]) DeleteById(id int64) int64 {
	u := gorm_v2.CreateEntityAndSetId(dao.model, id)
	/// 删除一条已有的记录（email 的主键值为 10）
	tx := dao.db.Delete(&u)
	// DELETE from emails where id=10;

	return tx.RowsAffected
}

func (dao *BaseDao[T]) GetDb() *gorm.DB {
	return dao.db
}

// BeginTx 开启事务：开启事务之后，必须使用开启事务返回的*gorm.DB, 而不是开启事务时使用*gorm.DB
func (dao *BaseDao[T]) BeginTx(opts ...*sql.TxOptions) *gorm.DB {
	return dao.db.Begin(opts...)
}

// RollbackTx 事务回滚
func (dao *BaseDao[T]) RollbackTx(dbTx *gorm.DB) {
	dbTx.Rollback()
}

// CommitTx 事务提交
func (dao *BaseDao[T]) CommitTx(dbTx *gorm.DB) {
	dbTx.Rollback()
}

// execTx 执行事务
func (dao *BaseDao[T]) ExecTx(txFn func(*gorm.DB) (error, interface{}), opts ...*sql.TxOptions) (error, interface{}) {
	dbTx := dao.db.Begin(opts...)

	defer func() {
		if r := recover(); r != nil {
			//fmt.Println("Recovered from:", r)
			dbTx.Rollback()
			panic(r)
		}
	}()

	err, data := txFn(dbTx)
	if err != nil {
		dbTx.Rollback()
	} else {
		dbTx.Commit()
	}

	return err, data
}

type PageParam struct {
	Page          int `json:"page"`     //当前页
	PageSize      int `json:"pageSize"` //当前页数据量
	QueryConditon int `json:"param"`    //当前页数据量
}

type PageResult struct {
	TotalPage int         `json:"totalPage"`  //总页数
	Total     int64       `json:"totalCount"` //总条数
	Page      int         `json:"page"`       //当前页
	PageSize  int         `json:"pageSize"`   //当前页数据量
	PrevPage  bool        `json:"prevPage"`   //是否存在上页
	NextPage  bool        `json:"nextPage"`   //是否存在下页
	List      interface{} `json:"list"`
}

func (dao *BaseDao[T]) QueryPage(pageParam PageParam) (PageResult, error) {
	db := dao.db
	var total int64

	// 先计数
	if err := db.Model(&dao.model).Count(&total).Error; err != nil {
		return PageResult{}, err
	}

	// 再查询
	db2 := db.Scopes(PaginateScope(pageParam.Page, pageParam.PageSize)).Find(&dao.model)
	if err := db2.Error; err != nil {
		return PageResult{}, err
	}

	var datas []T
	db2.Scan(datas)

	// 计算分页信息
	totalPage := int(math.Ceil(float64(total) / float64(pageParam.PageSize)))
	pagination := PageResult{
		Page:      pageParam.Page,
		PageSize:  pageParam.PageSize,
		Total:     total,
		TotalPage: totalPage,
		List:      datas,
	}

	return pagination, nil
}

// PaginateScope 分页Scope
func PaginateScope(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if page <= 0 {
			page = 1
		}
		if pageSize <= 0 {
			pageSize = 10
		}
		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}
